#include "ROT.h"
#include <string.h>
#include <ctype.h>
#include <map>
#include <vector>
#include <iostream>

using namespace std;

static map<int, map<char *, vector<struct preRot * >, ltstr > > cach;
static map<char *, vector<struct preRot *>, ltstr> searchCach;
static int hitNum = 0;
static int missNum = 0;
static int storeNum = 0;

void ROT::clearCache() {
	for (map<char *, vector<struct preRot *>, ltstr>::iterator it = searchCach.begin(); it != searchCach.end(); it++) {
		free((*it).first);
	}
	searchCach.clear();
	cout << "Cache cleared." << endl;
}

char *ROT::rotToA(const char *input) {
	int len = strlen(input);
	char *ret = new char[len + 1];
	
	int tmp;
	int rotation = (int)tolower(input[0]) - 97;
	for (int i = 0; i < len; i++) {
		tmp = (int)tolower(input[i]) - 97 - rotation;
		while (tmp < 0) tmp += 26;
		tmp = tmp % 27;
		tmp += 97;
		ret[i] = (char)tmp;
	}
	ret[len] = '\0';
	
	return ret;
}

void ROT::showStatistics() {
	cout << "Cache hits: " << hitNum << endl;
	cout << "Cache misses: " << missNum << endl;
	cout << "Cache stores: " << storeNum << endl;
	cout << "Cache size: " << searchCach.size() << endl;
	cout << "Percentage: " << ((float)hitNum / (hitNum + missNum)) << endl;
}

vector<struct preRot *> *ROT::findCorots(const char *input) {
	vector<struct preRot *> *ret = new vector<struct preRot *>;
	char *rotted = rotToA(input);
	char *rotted2 = (char *)malloc(sizeof(char) * (strlen(rotted) + 1));
	strcpy(rotted2, rotted);
	int rl = strlen(rotted);
	if (rl > 20) rl = 20;
	while (rl > 0) {
		rotted2[rl] = '\0';
		rl--;
		if (searchCach.find(rotted) != searchCach.end()) {
			delete ret;
			ret = &(*searchCach.find(rotted)).second;
			delete rotted;
			delete rotted2;
			hitNum++;
			return ret;
		}
	}
	delete rotted2;
	missNum++;
	
	for (map<int, map<char *, vector<struct preRot *>, ltstr> >::iterator it = cach.end(); it != cach.begin(); it--) {
		int length = it->first;
		for (map<char *, vector<struct preRot *>, ltstr>::iterator it2 = cach[length].begin(); it2 != cach[length].end(); it2++) {
			if (!strncmp(it2->first, rotted, length)) {
				for (vector<struct preRot *>::iterator it3 = it2->second.begin(); it3 != it2->second.end(); it3++) {
					ret->insert(ret->end(), *it3);
				}
			}
		}
	}

	if (strlen(rotted) < 20) {
		storeNum++;
		searchCach[rotted] = *ret;
	}

	return ret;
}


struct preRot *ROT::createPreRotStruct(const char *input) {
	int len = strlen(input);
	
	struct preRot *pr = new struct preRot;
	pr->prerot = new char[len + 1];
	pr->original = new char[len + 1];
	strncpy(pr->original, input, len +1);
	pr->rotation = (int)tolower(pr->original[0]) - 97;	
	pr->prerot = ROT::rotToA(pr->original);
	
	return pr;
}

bool ROT::checkIfIn(const char *input) {
	map<int, map<char *, vector<struct preRot *>, ltstr> >::iterator it;
	if ((it = cach.find(strlen(input))) == cach.end())
		return false;
		
	map<char *, vector<struct preRot *>, ltstr>::iterator it2;
	char *tmp = rotToA(input);
	it2 = it->second.find(tmp);
	delete tmp;
	if (it2 == it->second.end())
		return false;
	
	for (vector<struct preRot *>::iterator it3 = it2->second.begin(); it3 != it2->second.end(); it3++) {
		if (!strcasecmp((*it3)->original, input)) {
			return true;
		}
	}
	
	return false;
}


void ROT::rotAndStore(const char *input) {
	int len = strlen(input);
	
	map<int, map<char *, vector<struct preRot *>, ltstr> >::iterator it;
	
	if ((it = cach.find(len)) == cach.end()) {
		// This is the first word of this length that we've found. According to the docs
		// I am doing things overly complicated. According to my system, this has to be
		// done this way.
		map<char *, vector<struct preRot *>, ltstr> tmp;
		vector<struct preRot *> tmp1;
		struct preRot *pr = createPreRotStruct(input);

		tmp1.insert(tmp1.end(), pr);
		tmp[pr->prerot] = tmp1;
		cach[len] = tmp;
	} else {
		// This is not the first word of this length that we've seen. It may well be the
		// first one with this hash though.
		struct preRot *pr = createPreRotStruct(input);
		map<char *, vector<struct preRot *>, ltstr>::iterator it2;
		
		if ((it2 = it->second.find(pr->prerot)) == it->second.end()) {
			// This is the first of a given hash that we've seen. Let's do things the
			// hard way again, since I can't get things to work according to the
			// documentation.
			vector<struct preRot *> tmp;
			tmp.insert(tmp.end(), pr);
			it->second[pr->prerot] = tmp;
		} else {
			// We've even had another of this length before. In this case everything
			// works just fine!
			
			if (checkIfIn(pr->original)) return;
			it2->second.insert(it2->second.end(), pr);
		}
	}
}
